﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Threading;
using System.Windows.Threading;
using System.IO;
using Copy4Backup.Enums;

namespace Copy4Backup
{
	/// <summary>
	/// Interaction logic for ExecuteWindow.xaml
	/// </summary>
	public partial class ExecuteWindow : Window
	{
		enum Status
		{
			StatusReady,
			StatusCopying,
			StatusError
		}

		#region Fields
		/// <summary>
		/// State change event.
		/// </summary>
		private event EventHandler<ExecuteEventArgs> executeEvent_;

		private readonly Project project_;

		private Status status_;

		private long maxSize_;

		private object enumeratorLockObject_;

		private FileWalker.FileWalkEnumerator enumerator_;

		Thread workThread_;

		private bool execute_;

		#endregion Fields

		/// <summary>
		/// Prevents default ctor usage.
		/// </summary>
		private ExecuteWindow()
		{
		}

		public ExecuteWindow(Project project)
		{
			InitializeComponent();

			project_ = project;

			executeEvent_ = new EventHandler<ExecuteEventArgs>(ExecuteEventHandler);

			enumeratorLockObject_ = new object();
		}

		private void buttonStart_Click(object sender, RoutedEventArgs e)
		{
			
			bool isOK = true;

			// Check, a destination exists.
			if (!(isOK = Directory.Exists(project_.DestinationFolder)))
			{
				if (MessageBox.Show("The destination folder " + project_.DestinationFolder + " doesn't exist."
					+ System.Environment.NewLine + "Do you want to create it?",
					Title, MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
				{
					try
					{
						Directory.CreateDirectory(project_.DestinationFolder);
						isOK = true;
					}

					catch (UnauthorizedAccessException excp)
					{
						MessageBox.Show(String.Format("The caller does not have the required permission.{0}Error: {1}",
							System.Environment.NewLine, excp.Message),
							Title, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (ArgumentException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is not valid.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							Title, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (PathTooLongException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is too long valid.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							Title, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (DirectoryNotFoundException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is not valid.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							Title, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (NotSupportedException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" contains a colon character (:) that is not part of a drive label (\"C:\\\")..{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							Title, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (IOException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is read-only.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							Title, MessageBoxButton.OK, MessageBoxImage.Error);
					}
				}
			}

			if (isOK)
			{
				// Check, if the destination is empty.

				if (!(isOK = (0 == Directory.GetDirectories(project_.DestinationFolder).Length)))
				{
					if (MessageBox.Show("The destination folder " + project_.DestinationFolder + " is not empty."
						+ System.Environment.NewLine + "Do you want to clear the destination folder?",
						Title, MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
					{
						try
						{
							Directory.Delete(project_.DestinationFolder, true);
							Directory.CreateDirectory(project_.DestinationFolder);
							isOK = true;
						}

						catch (UnauthorizedAccessException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The caller does not have the required permission to delete the folder (\"{0}\").{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (ArgumentException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The folder name (\"{0}\") is not valid.{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (PathTooLongException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The folder name (\"{0}\") is not valid.{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (DirectoryNotFoundException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The folder (\"{0}\") doesn't exist or is not valid.{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (IOException excp)
						{
							System.Windows.MessageBox.Show(String.Format("Can't delete the folder (\"{0}\").{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}
					}
				}
			}

			if (isOK)
			{
				ComboBoxItem item = null;

				try
				{
					isOK = false;
					item = comboBoxMaxSize.SelectedItem as ComboBoxItem;
					if (null != item)
					{
						maxSize_ = long.Parse(item.Tag.ToString()) * 1024 * 1024; // MB to bytes.
						progressBarDiskUsage.Minimum = 0.0;
						progressBarDiskUsage.Maximum = maxSize_;
						progressBarDiskUsage.Value = 0.0;
						isOK = true;
					}
				}

				catch (FormatException excp)
				{
					System.Windows.MessageBox.Show(String.Format("The max (\"{0}\") has not valid format.{1}Error: {2}",
								item.Tag.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
				}

				catch (OverflowException excp)
				{
					System.Windows.MessageBox.Show(String.Format("The max (\"{0}\") has not valid value.{1}Error: {2}",
								item.Tag.ToString(),
								System.Environment.NewLine, excp.Message),
								Title, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
				}
			}

			if (isOK)
			{
				execute_ = true;
				workThread_ = new Thread(FileCopyThread);
				workThread_.Start();

				SetStatus(Status.StatusCopying);
			}
		}

		private void buttonStop_Click(object sender, RoutedEventArgs e)
		{
			execute_ = false;

			lock (enumeratorLockObject_)
			{
				if (null != enumerator_)
				{
					enumerator_.Stop();
				}
			}

			workThread_.Join();

			SetStatus(Status.StatusReady);
		}

		private void Window_Loaded(object sender, RoutedEventArgs e)
		{
			textBlockProjectName.Text = project_.Name;
			textBlockDiskNumber.Text = String.Empty;
			textBlockFileName.Text = String.Empty;
			textBlockTotalDiskSize.Text = String.Empty;

			SetStatus(Status.StatusReady);
		}

		private void SetStatusBar()
		{
			switch (status_)
			{
				case Status.StatusCopying:
					labelStatus.Content = "Copying...";
					break;
				case Status.StatusError:
					labelStatus.Content = "***Error";
					break;
				case Status.StatusReady:
					labelStatus.Content = "Ready";
					break;
				default:
					break;
			}
		}

		private void SetStatus(Status status)
		{
			status_ = status;

			switch (status_)
			{
				case Status.StatusReady:
					buttonStart.IsEnabled = true;
					buttonStop.IsEnabled = false;
					buttonCancel.IsEnabled = true;
					comboBoxMaxSize.IsEnabled = true;
					break;
				case Status.StatusCopying:
					buttonStart.IsEnabled = false;
					buttonStop.IsEnabled = true;
					buttonCancel.IsEnabled = false;
					comboBoxMaxSize.IsEnabled = false;
					break;
				case Status.StatusError:
					break;
				default:
					break;
			}

			SetStatusBar();
		}

		private void ExecuteEventHandler(object sender, ExecuteEventArgs args)
		{
			if (this.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
			{
				this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
				(ThreadStart)delegate()
				{
					ProcessEvent(args);
				}
				);
			}
			else
			{
				ProcessEvent(args);
			}
		}

		private void ProcessEvent(ExecuteEventArgs args)
		{
			switch (args.Action)
			{
				case ExecuteEvent.CopyFile:
					textBlockFileName.Text = args.FileName;
					textBlockTotalDiskSize.Text = args.Disk.Used.ToString("N0");
					textBlockDiskNumber.Text = args.Disk.Number.ToString();
					progressBarDiskUsage.Value = args.Disk.Used;
					break;
				case ExecuteEvent.Completed:
					SetStatus(Status.StatusReady);
					MessageBox.Show("Copying was completed", Title, MessageBoxButton.OK, MessageBoxImage.Information);
					break;
				default:
					break;
			}
		}

		private void FileCopyThread()
		{
			FileChangedHandler fch = new FileChangedHandler(project_.DestinationFolder, maxSize_);
			fch.FileChangedEvent += new EventHandler<FileChangedEventArgs>(FileChangedEventHandler);

			foreach (String folderFilter in project_.FolderFilters)
			{
				fch.AddFolderFilter(new FolderFilter(folderFilter));
			}

			foreach (String fileFilter in project_.FileFilters)
			{
				fch.AddFileFilter(new FileFilter(fileFilter));
			}

			foreach(String sourceFolder in project_.SourceFolders)
			{
				if (execute_)
				{
					lock (enumeratorLockObject_)
					{
						enumerator_ = new FileWalker.FileWalkEnumerator(sourceFolder);
					}

					enumerator_.AddHandler(fch);
					enumerator_.Enumerate();

					lock (enumeratorLockObject_)
					{
						enumerator_ = null;
					}	
				}
			}

			ExecuteEventArgs args = new ExecuteEventArgs(ExecuteEvent.Completed);
			executeEvent_(this, args);
		}

		void FileChangedEventHandler(object sender, FileChangedEventArgs e)
		{
			ExecuteEventArgs args = new ExecuteEventArgs(ExecuteEvent.CopyFile);
			args.FileName = e.FileInfoData.FullName;
			args.Disk = e.Disk;
			executeEvent_(this, args);
		}
	}
}
